package finalgame.Engine.pokemon;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.opengl.Texture;
import org.reflections.Reflections;

import finalgame.Game;
import finalgame.Engine.GeneratePokemon;
import finalgame.Engine.moves.Move;
import finalgame.Graphics.Location;
import finalgame.Graphics.Render2D;
import finalgame.Graphics.RenderEngine;
import finalgame.Graphics.Renderable;

public abstract class Pokemon extends Renderable {
	private static Map<String, Class<? extends Pokemon>> pokemon;
	private static boolean init = false;

	static {
		System.out.println("Moves init");
		pokemon = new LinkedHashMap<String, Class<? extends Pokemon>>();
		loadPokemon();
		init = true;
	}
	
	private static void loadPokemon() {
		if(init) return;
		Reflections reflections = new Reflections("finalgame",
				Game.class.getClassLoader());
		Set<Class<? extends Pokemon>> classes = reflections
				.getSubTypesOf(Pokemon.class);
		int i = 0;
		ArrayList<String> names = new ArrayList<String>();
		GeneratePokemon.genMap = new HashMap<String, Integer>();
		for (Class clazz : classes) {
			String clazzName = clazz.getSimpleName();
			names.add(clazzName);
			pokemon.put(clazzName.toLowerCase(), clazz);
			GeneratePokemon.genMap.put(clazzName, getGen(clazzName));
		}
		GeneratePokemon.pokemonlist = names.toArray(new String[names.size()]);
	}
	
	public static Class<? extends Pokemon> getPokemon(String pokemonName) {
		return pokemon.get(pokemonName);
	}
	
	public static Pokemon getNewPokemon(String pokemonName, int level) {
		try {
			Class<? extends Pokemon> pokemon = getPokemon(pokemonName.toLowerCase());
			Constructor<? extends Pokemon> cons = pokemon.getConstructor(Integer.TYPE);
			return (Pokemon) cons.newInstance(level);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}
	protected String nickname;
	protected int type;
	protected int experience, level;
	protected Boolean hasLeveled;
	// 0 = hitpoints, 1 = attack, 2 = defense, 3 = spattack, 4 = spdefense, 5 = speed
	protected double[] growths;
	protected double[] stats;
	protected String[] moves;
	protected int currentHP;

	public Pokemon(int t, double[] stat, double[] grow, int l) {
		type = t;
		level = l;
		experience = 0;
		growths = grow;
		stats = new double[6];
		for (int i = 0; i < grow.length; i++) {
			stats[i] = stat[i] + level * grow[i];
		}
		currentHP = (int) stats[0];
		moves = new String[4];
		name = "" + getNumberString();
	}
	public void addMove(String s, int index){
		moves[index] = s;
	}
	public int getHP() {
		return currentHP;
	}
	public String[] getMoves(){
		String[] moves = new String[this.moves.length];
		for(int i = 0; i < moves.length; i++)
			if(this.moves[i] != null)
				moves[i] = this.moves[i].substring(4); 
		return moves;
	}
	
	public void useMove(String moveName) {
		moveName = "move" + moveName;
	}
	
	// Removes passed in HP from Pokemon object, returns true if Pokemon faints,
	// false otherwise
	public Boolean removeHP(int amountRemoved) {
		currentHP -= amountRemoved;
		if (currentHP <= 0) {
			currentHP = 0;
			return true;
		}
		return false;
	}

	// Recursive method that checks if Pokemon should level up and levels up
	// until experience < experience needed to level again
	public Boolean LevelUp() {
		if (experience >= expToLevel()) {
			experience -= expToLevel();
			level++;
			addGrowths();
			updateMoves();
			Game.say("Your " +
					getName() +
					" grew to Lv. " +
					getLevel() + "!",
					"Alert");
			LevelUp();
			return true;
		}
		return false;
	}
	public abstract void updateMoves();
	private void addGrowths() {
		for (int i = 0; i < stats.length; i++) {
			stats[i] += growths[i];
		}
		currentHP += growths[0];
	}
	private int expToLevel() {
		return (int) (level * 3);
	}

	public void addExp(int gain) {
		experience += gain;
	}

	public int expGiven() {
		return (expToLevel() / 2) + 1;
	}

	public void setNick(String n) {
		nickname = n;
	}
	public int getMaxHP(){
		return (int) stats[0];
	}
	public int getAttack() {
		return (int) stats[1];
	}

	public int getDefense() {
		return (int) stats[2];
	}

	public int getSpAttack() {
		return (int) stats[3];
	}

	public int getSpDefense() {
		return (int) stats[4];
	}

	public int getSpeed() {
		return (int) stats[5];
	}
	public int getType() {
		return type;
	}

	public int getLevel() {
		return level;
	}
	
	public void update() {
		
	}
	
	public void setHP(int i){
		currentHP = i;
	}
	
	public int getNumber() {
		try {
			return getClass().getField("pokemonNumber").getInt(this);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return -1;
	}
	
	private Formatter f = null;
	public String getNumberString() {
		if(f == null) f = new Formatter();
		try {
			int id = getClass().getField("pokemonNumber").getInt(this);
			String id_string = "" + id;
			while(id_string.length() < 3) id_string = "0" + id_string;
			return id_string;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return "199";
	}
	
	public static int getGen(String name) {
		try {
			Pokemon p = getNewPokemon(name.toLowerCase(), 1);
			int gen = p.getClass().getField("generation").getInt(p);
			return gen;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return 5;
	}
	
	public enum Direction {
		DOWN, UP;
	}
	
	private Direction facing;
	public void render() {
		String textureId = getBack();
		if(facing.name().equals(Direction.DOWN.name())) textureId = getFront();
		Texture tex = RenderEngine.getSprite(textureId);
		Render2D.drawTex(myLoc.x(), myLoc.y(), tex);
		UnicodeFont font = Game.game.getFont("russian.lg");
		String text = "HP: " + currentHP;
		int width = font.getWidth(text);
		int height = font.getHeight(text);
		// Draw HP
		font.drawString(
			(float) myLoc.x() - width / 2,
			(float) myLoc.y() - tex.getImageHeight() / 2 - height + 3,
			text
		);
	}
	
	private void drawStats() {
		Texture tex = RenderEngine.getSprite(getBack());
		UnicodeFont font = Game.game.getFont("russian.lg");
		int border = 60;
		int height = font.getHeight("I");
		font.drawString(
				(float) myLoc.x() + tex.getImageWidth() / 2 + border,
				(float) myLoc.y() - tex.getImageHeight() / 2 - 2 * height + 3,
				"Exp up: " + (expToLevel() - experience)
		);
		font.drawString(
				(float) myLoc.x() + tex.getImageWidth() / 2 + border,
				(float) myLoc.y() - tex.getImageHeight() / 2 - height + 3,
				"Lv. " + level
		);
		font.drawString(
				(float) myLoc.x() + tex.getImageWidth() / 2 + border,
				(float) myLoc.y() - tex.getImageHeight() / 2 + 3,
				"Atk. " + getAttack()
		);
		font.drawString(
				(float) myLoc.x() + tex.getImageWidth() / 2 + border,
				(float) myLoc.y() - tex.getImageHeight() / 2 + height + 3,
				"Def. " + getDefense()
		);

		font.drawString(
				(float) myLoc.x() + tex.getImageWidth() / 2 + border,
				(float) myLoc.y() - tex.getImageHeight() / 2 + 2 * height + 3,
				"Spd. " + getSpeed()
		);
	}
	
	public void render(Location loc, Direction facing, boolean drawStats) {
		myLoc = loc;
		this.facing = facing;
		render();
		if(drawStats)
			drawStats();
	}

	public String getName() {
		return getClass().getSimpleName();
	}
}
